﻿#include "study11.h"
// C/C++ 标准库学习网站：https://legacy.cplusplus.com
// C/C++ 微软(Visual Studio)中文学习网站：https://learn.microsoft.com/zh-cn/cpp/cpp/?view=msvc-170

/*
需要调试的代码跟踪内存分配时，可以重载 new 和 delete 运算符，以便记录分配和释放的内存地址;
	然后通过一个全局结构体或全局变量来记录分配和释放的内存大小，便于分析内存泄漏和内存溢出问题。

左值和右值：
左值（lvalue）：
	一个表达式如果可以出现在赋值表达式的左侧，即可以被赋值，那么这个表达式就是左值。左值通常具有持久的存储（如栈或堆上的变量）。例如，变量名 a 就是一个左值。
右值（rvalue）：
	一个表达式如果不能出现在赋值表达式的左侧，那么这个表达式就是右值。右值通常是指那些在表达式结束时不再使用的临时值。例如，字面量（如 10）就是一个右值。
在 C++ 中，左值（有地址的值）和右值是指表达式的值，左值可以出现在赋值号左边，右值则不能。
	常见的左值：
		1. 变量
		2. 数组元素
		4. 成员变量
	常见的右值：
		1. 字面值常量
		2. 表达式
		4. 函数调用的结果
		4. 临时对象
	左值和右值的区别：
		左值：可以被赋值，可以被移动，可以被复制，可以被销毁
		右值：不能直接被赋值给非 const 左值引用，可以被移动，可以被销毁
	注意：不能将右值赋值给左值引用，因为非 const 引用必须是左值；左值是有某种储存支持的变量，右值更像是临时的值。

持续集成是指每次提交代码到 git 仓库时，自动运行编译、测试、打包等一系列流程，并在出现错误时通知开发人员。
	持续集成的好处：
		降低开发人员的工作压力，提升软件质量
	持续集成环境使用：
		GitHub Actions：GitHub 提供的持续集成服务，可以自动编译、测试、打包代码，并在出现错误时通知开发人员。
		Jenkins：开源的持续集成服务，可以与众多开发工具集成，支持多种语言和框架。
		Azure Pipelines：微软 Azure 提供的持续集成服务，可以与众多开发工具集成，支持多种语言和框架。

当函数传递参数的过程中进行变量的计算等操作时，可能是 C++ 编译器未定义的行为，导致程序崩溃或产生无法预估的结果；
	所以实际编程中，尽量避免这类操作，以避免出现未定义行为。这里的测试说明使用 VS 的 MSVC 编译器；
	测试 gcc/clang 编译器编译代码（小日子的大佬开源项目）网址：https://wandbox.org/
	gcc 官网网址：https://gcc.gnu.org/
*/

AllocationMetrics allocation_main; // 全局变量，记录分配和释放的内存大小

struct Obj {};
static Obj* get_obj() { return nullptr; } // 错误：new 运算符返回的指针类型不正确，应该是指向 Obj 类型的指针

void print_sun(int x, int y, int z) {
	std::cout << "x: " << x << ", y: " << y << ", z: " << z << std::endl;
	std::cout << "sum = " << x + y + z << std::endl;
}

int main()
{
	using namespace std; // 命名空间声明
	// 测试内存跟踪
	print_start("测试内存跟踪");
	struct a4rr_obj {
		uint64_t a4, b, c;
	}; // 自定义结构体
	print_memory_usage();
	string* s1 = new string("test_string"); // 左值，分配内存
	print_memory_usage();
	{
		unique_ptr<a4rr_obj> p1 = make_unique<a4rr_obj>(); // 右值，分配内存
		print_memory_usage();
	}
	print_memory_usage();
	delete s1; // 释放内存
	print_memory_usage();
	print_end("测试内存跟踪");

	// 左右值测试
	print_start("左右值测试");
	//int& a41 = 1; // err: 左值不能绑定到右值引用
	int a1 = 1; // 左值
	int&& a2 = 10; // ok: a42 是一个右值引用（两个 & 符号），右值可以绑定到右值引用，C++11 引入的
	// int& a3 = 100; // error: 左值引用不能绑定到右值
	const int& a1_cite = a1; // 这里常量引用的是左值
	int const& a3 = 100; // 常量引用，同时兼容左值和右值，作为函数参数类型声明时也一样
	a2 = 20; // 重新将右值引用 a2 绑定到右值 20
	//a3 = 30; // error: 常量引用不能重新绑定到值
	a1 = 1000; // 重新将左值 a1 绑定到值 10
	cout << "a1: " << a1 << "，a2: " << a2 << ", a3: " << a3 << ", a1_cite: " << a1_cite << endl;
	// 上面输出：a1_cite: 1000 
	print_end("左右值测试");

	// 测试错误代码：
	print_start("错误代码测试");
	/*for (int i = 0; i < 10; ++i)
		for (int j = 0; j < 10; ++i)
			cout << "i: " << i << ", j: " << j << endl; */ // 错误：i 变量被重复定义，导致 i 的值在循环中不断变化，导致结果不正确
	const char* c1 = "test";
	char* buffer = new char[strlen(c1)]; // 隐式错误：new 运算符计算的长度少一个字符，导致 buffer 数组越界
	// 正确的写法：char* buffer = new char[strlen(c1) + 1];
	print_memory_usage(); // 4 字节
	strcpy(buffer, c1); // 错误：strcpy 要求目标缓冲区大小大于源字符串长度，导致缓冲区溢出
	cout << buffer << endl; // 输出：test\0
	Obj* obj = get_obj(); // 没有使用 new
	if (obj) { delete obj; } // 错误：这里可能返回 nullptr，所以不会进入 if 语句
	//delete[] buffer; // 释放内存，error: buffer 指针越界 free
	print_memory_usage(); // 4 字节，说明内存泄露 4 字节
	print_end("错误代码测试");

	// 测试函数参数计算顺序
	print_start("函数参数计算顺序测试");
	int a4 = 0, b4 = 0, c4 = 0;
	print_sun(a4++, a4++, a4++); // Debug 输出：x: 2, y: 1, z: 0, sum = 3
	// Release 输出：x: 0, y: 0, z: 0, sum = 0
	a4 = 0, b4 = 0, c4 = 0;
	print_sun(++a4, ++b4, ++c4); // Debug、Release 都输出：x: 1, y: 1, z: 1, sum = 3
	a4 = 0, b4 = 0, c4 = 0;
	print_sun(++a4, a4++, ++a4); // Debug 输出：x: 3, y: 1, z: 3, sum = 7
	// Release 输出：x: 2, y: 1, z: 2, sum = 5
	print_end("函数参数计算顺序测试");

	system("pause");
	return 0;
}